﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;

namespace System.Peppers.Win32
{
    /// <summary>
    /// An implementation of IStream for passing managed streams to interop functions
    /// </summary>
    [SuppressUnmanagedCodeSecurity]
    public sealed class NativeStream : IStream
    {
        private Stream _dataStream = null;
        private long _virtualPosition = -1L;

        /// <summary>
        /// Constuctor wrapping a managed Stream
        /// *NOTE: loads entired stream into a buffer if it cannot seek
        /// </summary>
        /// <param name="stream">Stream for interop use</param>
        public NativeStream(Stream stream)
        {
            if (!stream.CanSeek)
            {
                int num;
                byte[] buffer = new byte[0x100];
                int num2 = 0;
                do
                {
                    if (buffer.Length < (num2 + 0x100))
                    {
                        byte[] buffer2 = new byte[buffer.Length * 2];
                        Array.Copy(buffer, buffer2, buffer.Length);
                        buffer = buffer2;
                    }
                    num = stream.Read(buffer, num2, 0x100);
                    num2 += num;
                }
                while (num != 0);
                this._dataStream = new MemoryStream(buffer);
            }
            else
            {
                this._dataStream = stream;
            }
        }

        private void ActualizeVirtualPosition()
        {
            if (this._virtualPosition != -1L)
            {
                if (this._virtualPosition > this._dataStream.Length)
                {
                    this._dataStream.SetLength(this._virtualPosition);
                }
                this._dataStream.Position = this._virtualPosition;
                this._virtualPosition = -1L;
            }
        }

        /// <summary>
        /// Required implementation for IStream
        /// </summary>
        /// <returns>null</returns>
        public IStream Clone()
        {
            NotImplemented();
            return null;
        }

        /// <summary>
        /// Required implementation for IStream
        /// </summary>
        /// <param name="grfCommitFlags"></param>
        public void Commit(int grfCommitFlags)
        {
            this._dataStream.Flush();
            this.ActualizeVirtualPosition();
        }

        /// <summary>
        /// Required implementation for IStream
        /// </summary>
        /// <param name="pstm">??</param>
        /// <param name="cb">??</param>
        /// <param name="pcbRead">??</param>
        /// <returns>??</returns>
        public long CopyTo(IStream pstm, long cb, long[] pcbRead)
        {
            int num = 0x1000;
            IntPtr buf = Marshal.AllocHGlobal(num);
            if (buf == IntPtr.Zero)
            {
                throw new OutOfMemoryException();
            }
            long num2 = 0L;
            try
            {
                while (num2 < cb)
                {
                    int length = num;
                    if ((num2 + length) > cb)
                    {
                        length = (int)(cb - num2);
                    }
                    int len = this.Read(buf, length);
                    if (len == 0)
                    {
                        break;
                    }
                    if (pstm.Write(buf, len) != len)
                    {
                        throw EFail("Wrote an incorrect number of bytes");
                    }
                    num2 += len;
                }
            }
            finally
            {
                Marshal.FreeHGlobal(buf);
            }
            if ((pcbRead != null) && (pcbRead.Length > 0))
            {
                pcbRead[0] = num2;
            }
            return num2;
        }

        private static ExternalException EFail(string msg)
        {
            ExternalException exception = new ExternalException(msg, -2147467259);
            throw exception;
        }

        /// <summary>
        /// Required implementation for IStream
        /// </summary>
        /// <returns>Managed stream</returns>
        public Stream GetDataStream()
        {
            return this._dataStream;
        }

        /// <summary>
        /// Required implementation for IStream
        /// </summary>
        /// <param name="libOffset">??</param>
        /// <param name="cb">??</param>
        /// <param name="dwLockType">??</param>
        public void LockRegion(long libOffset, long cb, int dwLockType)
        { }

        private static void NotImplemented()
        {
            ExternalException exception = new ExternalException("Not implemented.", -2147467263);
            throw exception;
        }

        /// <summary>
        /// Required implementation for IStream
        /// </summary>
        /// <param name="buf">??</param>
        /// <param name="length">??</param>
        /// <returns>??</returns>
        public int Read(IntPtr buf, int length)
        {
            byte[] buffer = new byte[length];
            int num = this.Read(buffer, length);
            Marshal.Copy(buffer, 0, buf, length);
            return num;
        }

        /// <summary>
        /// Required implementation for IStream
        /// </summary>
        /// <param name="buffer">??</param>
        /// <param name="length">??</param>
        /// <returns>??</returns>
        public int Read(byte[] buffer, int length)
        {
            this.ActualizeVirtualPosition();
            return this._dataStream.Read(buffer, 0, length);
        }

        /// <summary>
        /// Required implementation for IStream
        /// </summary>
        public void Revert()
        {
            NotImplemented();
        }

        /// <summary>
        /// Required implementation for IStream
        /// </summary>
        /// <param name="offset">??</param>
        /// <param name="origin">??</param>
        /// <returns>??</returns>
        public long Seek(long offset, int origin)
        {
            long virtualPosition = this._virtualPosition;
            if (this._virtualPosition == -1L)
            {
                virtualPosition = this._dataStream.Position;
            }
            long num2 = this._dataStream.Length;
            switch (origin)
            {
                case 0:
                    if (offset > num2)
                    {
                        this._virtualPosition = offset;
                        break;
                    }
                    this._dataStream.Position = offset;
                    this._virtualPosition = -1L;
                    break;

                case 1:
                    if ((offset + virtualPosition) > num2)
                    {
                        this._virtualPosition = offset + virtualPosition;
                        break;
                    }
                    this._dataStream.Position = (virtualPosition + offset);
                    this._virtualPosition = -1L;
                    break;

                case 2:
                    if (offset > 0L)
                    {
                        this._virtualPosition = num2 + offset;
                        break;
                    }
                    this._dataStream.Position = (num2 + offset);
                    this._virtualPosition = -1L;
                    break;
            }
            if (this._virtualPosition != -1L)
            {
                return this._virtualPosition;
            }
            return this._dataStream.Position;
        }

        /// <summary>
        /// Required implementation for IStream
        /// </summary>
        /// <param name="value">??</param>
        public void SetSize(long value)
        {
            this._dataStream.SetLength(value);
        }

        /// <summary>
        /// Required implementation for IStream
        /// </summary>
        /// <param name="pstatstg">??</param>
        /// <param name="grfStatFlag">??</param>
        public void Stat(IntPtr pstatstg, int grfStatFlag)
        {
            STATSTG statstg = new STATSTG();
            statstg.cbSize = this._dataStream.Length;
            Marshal.StructureToPtr(statstg, pstatstg, true);
        }

        /// <summary>
        /// Required implementation for IStream
        /// </summary>
        /// <param name="libOffset">??</param>
        /// <param name="cb">??</param>
        /// <param name="dwLockType">??</param>
        public void UnlockRegion(long libOffset, long cb, int dwLockType)
        { }

        /// <summary>
        /// Required implementation for IStream
        /// </summary>
        /// <param name="buf">??</param>
        /// <param name="length">??</param>
        /// <returns>??</returns>
        public int Write(IntPtr buf, int length)
        {
            byte[] buffer = new byte[length];
            Marshal.Copy(buf, buffer, 0, length);
            return this.Write(buffer, length);
        }

        /// <summary>
        /// Required implementation for IStream
        /// </summary>
        /// <param name="buffer">??</param>
        /// <param name="length">??</param>
        /// <returns>??</returns>
        public int Write(byte[] buffer, int length)
        {
            this.ActualizeVirtualPosition();
            this._dataStream.Write(buffer, 0, length);
            return length;
        }

        [StructLayout(LayoutKind.Sequential)]
        private class STATSTG
        {
            public IntPtr pwcsName;
            public int type = 0;
            [MarshalAs(UnmanagedType.I8)]
            public long cbSize;
            [MarshalAs(UnmanagedType.I8)]
            public long mtime;
            [MarshalAs(UnmanagedType.I8)]
            public long ctime;
            [MarshalAs(UnmanagedType.I8)]
            public long atime;
            [MarshalAs(UnmanagedType.I4)]
            public int grfMode;
            [MarshalAs(UnmanagedType.I4)]
            public int grfLocksSupported;
            public int clsid_data1;
            [MarshalAs(UnmanagedType.I2)]
            public short clsid_data2;
            [MarshalAs(UnmanagedType.I2)]
            public short clsid_data3;
            [MarshalAs(UnmanagedType.U1)]
            public byte clsid_b0;
            [MarshalAs(UnmanagedType.U1)]
            public byte clsid_b1;
            [MarshalAs(UnmanagedType.U1)]
            public byte clsid_b2;
            [MarshalAs(UnmanagedType.U1)]
            public byte clsid_b3;
            [MarshalAs(UnmanagedType.U1)]
            public byte clsid_b4;
            [MarshalAs(UnmanagedType.U1)]
            public byte clsid_b5;
            [MarshalAs(UnmanagedType.U1)]
            public byte clsid_b6;
            [MarshalAs(UnmanagedType.U1)]
            public byte clsid_b7;
            [MarshalAs(UnmanagedType.I4)]
            public int grfStateBits;
            [MarshalAs(UnmanagedType.I4)]
            public int reserved;
        }
    }
}
